#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>

#include "../../projectevent.h"
#include "../../projecteventbus.h"
#include "../../project_p.h"

#include "cmakeprojectmanager.h"

INIT_PROJECT_MANAGER_PLUGIN_FACTORY( CMakeProjectManager )
INIT_PROJECT_MANAGER_PLUGIN_NAME( "CMake Project Manager Plugin\0" )
INIT_PROJECT_MANAGER_PLUGIN_VERSION( -1 )
INIT_PROJECT_MANAGER_PLUGIN_AUTHOR( "The Cine Developer" )

void CMakeProjectManager::RegisterEvent( ProjectEventBus* eventBus )
{
    /// import/export
    eventBus->AttachListener( Events::ProjectImport, this );
    eventBus->AttachListener( Events::ProjectExport, this );
    
    /// tmp FIXME: remove it ;)
//     eventBus->AttachListener( Events::ProjectPrintInfo, this );
}

bool CMakeProjectManager::HandleEvent( Events::Event* event )
{
    std::cerr << "cmake handle event\n";
    /// NOTE: initialize shared d-pointer here
    switch ( event->GetType() ) {
        /// import/export
        case Events::ProjectImport: {
            d = ((ProjectImportEvent*)(event))->d;
            std::string str = d->project_mainfile;/*((ProjectImportEvent*)(event))->data;*/
            importProjectFiles( str );
            break;
        }
        case Events::ProjectExport: {
            d = ((ProjectExportEvent*)(event))->d;
            std::string str = d->project_mainfile;/*((ProjectExportEvent*)(event))->data;*/
            exportProjectFiles( str );
            break;
        }
//         case Events::ProjectPrintInfo:
//             d = ((ProjectPrintInfoEvent*)(event))->d;
//             printInfo();
//             break;
            /// tmp member
        default:
            std::cerr << "Unexpected Event Here!" << std::endl;
    }
    std::cerr << "catch event from cmake project sub plugin" << std::endl;
    printInfo();
    return false;
}

void CMakeProjectManager::importProjectFiles( const std::string& inbasefile )
{
    std::cerr << "import: " << inbasefile << std::endl;
    std::ifstream fp( inbasefile.c_str(), std::ifstream::in );
    if ( !fp.good() )
        return;

    // '/' is portable
    size_t found = inbasefile.find_last_of( '/' );
    if ( found == std::string::npos )
        d->project_basedir = "./";
    else
        d->project_basedir = inbasefile.substr( 0, found );

    parse( fp );

    fp.close();

    d->is_modified = false;
    std::cerr << "import finished\n";
}

void CMakeProjectManager::exportProjectFiles( const std::string& outbasefile )
{
    std::cerr << "export: " << outbasefile << std::endl;
    std::ofstream fp( outbasefile.c_str(), std::ofstream::out );
    if ( !fp.good() )
        return;

    fp << "project( " << d->project_name << " )" << std::endl;
    
    fp << "cmake_minimum_required( VERSION 2.6 )" << std::endl;

    for ( int q=0; q<d->project_targets.size(); q++ ) {
        ProjectTarget target = d->project_targets[q];
        fp << "include_directories( ";
        for ( int i=0; i<target.project_includedirs.size(); i++ )
            fp << target.project_includedirs[i] << ' ';
        fp << ')' << std::endl;

        /// FIXME: sets is not supported ATM
//         std::map< std::string, std::vector<std::string> >::const_iterator it = target.target_filesets.begin();
//         for ( ; it!=target.target_filesets.end(); it++ ) {
//             fp << "set( " << (*it).first << ' ';
//             for ( int i=0; i<(*it).second.size(); i++ )
//                 fp << (*it).second[i] << ' ';
//             fp << ')' << std::endl;
//         }

        fp << "add_executable( " << target.target_bin << ' ';
        /// FIXME: sets is not supported ATM
        for ( int i=0; i<target.target_files.size(); i++ ) {
            fp << target.target_files[i] << ' ';
            std::cerr << target.target_files[i] << ' ';
        }
//         std::set<std::string>::const_iterator first = target.extracted_files.begin();
//         std::set<std::string>::const_iterator end = target.extracted_files.end();
//         while ( first != end ) {
//             fp << *first << ' ';
//             ++first;
//         }
        fp << ')' << std::endl;

        fp << "target_link_libraries( " << target.target_bin << ' ';
        for ( int i=0; i<target.project_linktargets.size(); i++ )
            fp << target.project_linktargets[i] << ' ';
        fp << ')' << std::endl;

        /// TODO: more compiler definitions here...
        fp << "add_definitions( ";
        if ( d->optflags.warning_all )
            fp << "-Wall ";
        fp << ')' << std::endl;
    }

    fp.close();

    d->is_modified = false;
}

void CMakeProjectManager::printInfo()
{
    std::cerr << "=================================" << std::endl;
    std::cerr << "project:\t" << d->project_name << std::endl << std::endl;
    for ( int q=0; q<d->project_targets.size(); q++ ) {
        ProjectTarget target = d->project_targets[q];
        std::cerr << "target:\t" << target.target_bin << std::endl;
        std::cerr << "files:\t";
        for ( int i=0; i<target.target_files.size(); i++ )
            std::cerr << target.target_files[i] << ' ';
        std::cerr << std::endl;
        std::cerr << "Ex:\t";
        std::set<std::string>::const_iterator first = target.extracted_files.begin();
        std::set<std::string>::const_iterator end = target.extracted_files.end();
        while ( first != end ) {
            std::cerr << *first << ' ';
            ++first;
        }
        std::cerr << std::endl;
        std::cerr << "inclu:\t";
        for ( int i=0; i<target.project_includedirs.size(); i++ )
            std::cerr << target.project_includedirs[i] << ' ';
        std::cerr << std::endl;
        std::cerr << "link:\t";
        for ( int i=0; i<target.project_linktargets.size(); i++ )
            std::cerr << target.project_linktargets[i] << ' ';
        std::cerr << std::endl;
        std::cerr << "sets:\t";
        std::map< std::string, std::vector<std::string> >::const_iterator it = target.target_filesets.begin();
        for ( ; it!=target.target_filesets.end(); it++ ) {
            std::cerr << (*it).first << " --- ";
            for ( int i=0; i<(*it).second.size(); i++ )
                std::cerr << (*it).second[i] << ' ';
            std::cerr << std::endl;
        }
        std::cerr << std::endl;
    }
}

void CMakeProjectManager::extract_contents( std::ifstream& fp, std::string& str, std::vector<std::string>& contents )
{
    size_t found;
    std::string nextline;
    size_t begin, end;
    
    found = str.find_first_of( '(' );
    while ( found == std::string::npos && getline( fp, nextline ) ) {
        if ( nextline.compare( 0, 1, "#" ) == 0 ) {
            /// # comment line
            continue;
        }
        str.append( nextline );
        found = str.find_first_of( '(' );
    }
    
    begin = found;
    
    found = str.find_last_of( ')' );
    while ( found == std::string::npos && getline( fp, nextline ) ) {
        if ( nextline.compare( 0, 1, "#" ) == 0 ) {
            /// # comment line
            continue;
        }
        str.append( nextline );
        found = str.find_last_of( ')' );
    }
    
    end = found;
    
    std::string content_str = str.substr( begin+1, end-begin-1 );
    
    std::stringstream content_stream( content_str );
    std::string item;
    while ( content_stream >> item ) {
        contents.push_back( item );
    }
}

void CMakeProjectManager::extract_filesets( ProjectTarget* target )
{
    for ( int i=0; i<target->target_files.size(); i++ ) {
        std::string str = target->target_files[i];
        if ( str.compare( 0, 1, "$" ) == 0 ) {
            /// ${app_SRCS}
            size_t begin = str.find_first_of( '{' );
            size_t end = str.find_last_of( '}' );
            std::string setsname = str.substr( begin+1, end-begin-1 );
            // try to find the set macro and extract
            std::map< std::string, std::vector<std::string> >::iterator it = target->target_filesets.find( setsname );
            if ( it != target->target_filesets.end() ) {
                std::vector<std::string> v2 = target->target_filesets[ setsname ];
                target->extracted_files.insert( v2.begin(), v2.end() );
            }
        }
    }
}

void CMakeProjectManager::parse( std::ifstream& fp )
{
    std::cerr << "cmake parse\n";
    std::string line;
    std::vector<std::string> items;
    ProjectTarget target;
    while ( getline( fp, line ) ) {
        if ( line.compare( 0, 1, "#" ) == 0 ) {
            /// # comment line
            continue;
        }
        if ( line.compare( 0, 7, "PROJECT" ) == 0
            || line.compare( 0, 7, "project" ) == 0 ) {
            /// PROJECT(appname)
            extract_contents( fp, line, items );
            d->project_name = items.front();
        }
        else if ( line.compare( 0, 19, "INCLUDE_DIRECTORIES" ) == 0
            || line.compare( 0, 19, "include_directories" ) == 0 ) {
            /// INCLUDE_DIRECTORIES(${xxx_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
            extract_contents( fp, line, target.project_includedirs );
        }
        else if ( line.compare( 0, 14, "ADD_EXECUTABLE" ) == 0
            || line.compare( 0, 14, "add_executable" ) == 0 ) {
            /// ADD_EXECUTABLE(appbin main.cpp)
            extract_contents( fp, line, items );
            target.target_bin = items.front();
            items.erase( items.begin() );
            target.target_files = items;
        }
        else if ( line.compare( 0, 21, "TARGET_LINK_LIBRARIES" ) == 0
            || line.compare( 0, 21, "target_link_libraries" ) == 0 ) {
            /// TARGET_LINK_LIBRARIES(appbin ${xxx_LIBRARY})
            extract_contents( fp, line, target.project_linktargets );
            target.project_linktargets.erase( target.project_linktargets.begin() );
        }
        else if ( line.compare( 0, 3, "SET" ) == 0
            || line.compare( 0, 3, "set" ) == 0 ) {
            /// SET(app_SRCS appmain.cpp)
            extract_contents( fp, line, items );
            std::string filesetsname = items.front();
            items.erase( items.begin() );
            target.target_filesets[ filesetsname ] = items;
        }
        else if ( line.compare( 0, 16, "ADD_SUBDIRECTORY" ) == 0
            || line.compare( 0, 16, "add_subdirectory" ) == 0 ) {
            /// ADD_SUBDIRECTORY(module)
            extract_contents( fp, line, items );
            std::string subdir = items.front();
            std::ifstream subdir_fp( ( d->project_basedir + '/' + subdir + "/CMakeLists.txt" ).c_str() );
            if ( subdir_fp.good() ) {
                // recursive calling
                parse( subdir_fp );
                subdir_fp.close();
            }
        }
        else {
            std::cerr << "skip line" << std::endl;
        }
        items.clear();
    }
    
    extract_filesets( &target );

    d->project_targets.clear();
    d->project_targets.push_back( target );
    std::cerr << "cmake parse finished\n";
}
